package com.vmj.instructions.references;

import com.vmj.common.BytesReader;
import com.vmj.heap.*;
import com.vmj.heap.VMClass;
import com.vmj.heap.VMObject;
import com.vmj.instructions.base.Instruction;
import com.vmj.instructions.base.MethodInvokeLogic;
import com.vmj.rtda.Frame;

/**
 * Created by yangshoujun on 2017/9/2 下午8:03.
 */
public class InvokeInterface implements Instruction {

    int index;

    public void fetchOperands(BytesReader reader) {
        index = reader.readShort();
        reader.readByte();
        reader.readByte();
    }

    public void execute(Frame frame) {
        VMClass currentCls = frame.getMethod().getClazz();
        RuntimeConstantPool cp = currentCls.getRuntimeConstantPool();
        InterfaceMethodRef methodRef = (InterfaceMethodRef) cp.getConstant(this.getIndex()).getValue();

        Method resovledMethod = methodRef.resolvedInterfaceMethod();
        if(resovledMethod.isStatic() || resovledMethod.isPrivate()) {
            throw new RuntimeException("java.lang.IncompatibalClassChangeError");
        }
        VMObject thisRef = frame.getOperandStack().getRefFromTop(resovledMethod.getArgSlotCount() - 1);
        if(thisRef == null) {
            throw new RuntimeException("java.lang.NullPointerException");
        }
        if(!thisRef.getClazz().isImplements(methodRef.getClazz())) {
            throw new RuntimeException("java.lang.IncompatibalClassChangeError");
        }

        Method methodToBeInvoked = MethodRef.lookupMethodInClass(thisRef.getClazz(), methodRef.getName(), methodRef.getDescriptor());
        if(methodToBeInvoked == null || methodToBeInvoked.isAbstract()) {
            throw new RuntimeException("java.lang.AbstractMethodError");
        }
        if(!methodToBeInvoked.isPublic()) {
            throw new RuntimeException("java.lang.IllegalAccessError");
        }

        MethodInvokeLogic.invokeMethod(frame, methodToBeInvoked);
    }

    public int getIndex() {
        return index;
    }
}
